#include <stdio.h>
#include <stdarg.h>
#include <types.h>
#include <ctype.h>
#include <console.h>


static int vprintfmt(void (*putc)(uint16_t c),const char *fmt, va_list ap);
static inline uint8_t isflag(int c);
static inline uint8_t ismodifier(int c);
static int64_t getint(va_list *ap, uint8_t modifiers);
static uint64_t getuint(va_list *ap, uint8_t modifiers);
static void printint(void (*putc)(uint16_t c), int64_t uinteger, uint8_t base,
				uint8_t flags, uint32_t width, uint32_t prec, uint32_t *count);
static void printuint(void (*putc)(uint16_t c), uint64_t uinteger, uint8_t base,
				uint8_t flags, uint32_t width, uint32_t prec, uint32_t *count);
static void printstring(void (*putc)(uint16_t c), char *string,
				uint32_t prec, uint32_t *count);


int	
printf(const char *fmt, ...)
{
	va_list ap;
	int ret;
	
	va_start(ap, fmt);
	ret = vprintf(fmt, ap);
	va_end(ap);
	
	return ret;
}

int 
vprintf(const char *fmt, va_list ap)
{
	return vprintfmt(vga_putc, fmt, ap);;
}

static int
vprintfmt(void (*putc)(uint16_t c), const char *fmt, va_list ap)
{
	uint32_t count = 0;
	uint8_t format = 0;
	uint8_t flags, modifiers, base;
	uint32_t width, prec;
	
	int64_t integer;
	uint64_t uinteger;
	
	while( *fmt != '\0' ){
		
		if( *fmt != '%' && !format ){
			putc((uint16_t)*fmt);
			fmt++;
			count++;
			continue;
		}

		if( !format ){
			fmt++;
			format = 1;
			flags = 0;
			modifiers = 0;
			width = 0;
			prec = 0;
			base = 0;
		}
		
		if( isflag(*fmt) ){
			switch(*fmt){
				case '-':
					flags |= FLAGS_MINUS;
					break;
				case '+':
					flags |= FLAGS_PLUS;
					break;
				case ' ':
					flags |= FLAGS_SPACE;
					break;
				case '0':
					flags |= FLAGS_ZERO;
					break;
			}
			fmt++;
			continue;
		}
		
		if( isdigit(*fmt) ){
			while( isdigit(*fmt) ){
				width *= BASE_DEC;
				width += *fmt - ASCII_ZERO;
				fmt++;
			}
		}
		
		if( *fmt == '.' ){
			fmt++;
			while( isdigit(*fmt) ){
				prec *= BASE_DEC;
				prec += *fmt - ASCII_ZERO;
				fmt++;
			}
		}
		
		if( ismodifier(*fmt) ){
			switch(*fmt){
				case 'h':
					modifiers |= MODIFIER_H;
					break;
				case 'l':
					modifiers |= MODIFIER_L;
					break;
			}
			fmt++;
		}

		switch(*fmt){
			case 'd':
			case 'i':
				base = BASE_DEC;
				integer = getint(&ap, modifiers);
				if( integer < 0 ){
					putc('-');
				}else if( flags & FLAGS_PLUS ){
					putc('+');
				}else if( flags & FLAGS_SPACE ){
					putc(' ');
				}
				printint(putc, integer, base, flags, width, prec, &count);
				format = 0;
				fmt++;
				break;
			case 'o':
				if( !base ){
					base = BASE_OCT;
				}
			case 'x':
				if( !base ){
					base = BASE_HEX;
				}
			case 'u':
				if( !base ){
					base = BASE_DEC;
				}
				uinteger = getuint(&ap, modifiers);
				if( flags & FLAGS_PLUS ){
					putc('+');
				}else if( flags & FLAGS_SPACE ){
					putc(' ');
				}
				printuint(putc, uinteger, base, flags, width, prec, &count);
				format = 0;
				fmt++;
				break;
			case 'c':
				putc(va_arg(ap, int));
				format = 0;
				count++;
				fmt++;
				break;
			case 's':
				printstring(putc, va_arg(ap, char*), prec, &count);
				format = 0;
				fmt++;
				break;
		}
		
		if( format ){
			return -1;
		}
	}
	
	return count;
}

static inline uint8_t
isflag(int c)
{
	return ( (c == '-') || (c == '+') || (c == ' ') || (c == '0') ) ? 1 : 0;
}

static inline uint8_t
ismodifier(int c){
	return ( (c == 'l') || (c == 'h') ) ? 1 : 0;
}

static int64_t
getint(va_list *ap, uint8_t modifiers)
{
	int64_t integer;
	
	if( modifiers & MODIFIER_H ){
		integer = (int16_t)va_arg(*ap, int32_t);
	}else if( modifiers & MODIFIER_L ){
		integer = va_arg(*ap, int64_t);
	}else{
		integer = va_arg(*ap, int32_t);
	}
	
	return integer;
}


static uint64_t
getuint(va_list *ap, uint8_t modifiers)
{
	uint64_t uinteger;
	
	if( modifiers & MODIFIER_H ){
		uinteger = (uint16_t)va_arg(*ap, uint32_t);
	}else if( modifiers & MODIFIER_L ){
		uinteger = va_arg(*ap, uint64_t);
	}else{
		uinteger = va_arg(*ap, uint32_t);
	}
	
	return uinteger;
}

static void
printint(void (*putc)(uint16_t c), int64_t integer, uint8_t base,
		 uint8_t flags, uint32_t width, uint32_t prec, uint32_t *count)
{
	uint64_t uinteger;
	
	uinteger = (uint64_t)((integer < 0 ? -1 : 1) * integer);
	printuint(putc, uinteger, base, flags, width, prec, count);
}

static void
printuint(void (*putc)(uint16_t c), uint64_t uinteger, uint8_t base,
		 uint8_t flags, uint32_t width, uint32_t prec, uint32_t *count)
{
	uint16_t pad;
	uint8_t num_digits = 1;
	uint64_t divider = base;
	uint64_t temp = uinteger;
	
	// Determine the number of digits to print.
	while( temp /= base ){
		num_digits++;
	}
	
	// Set padding character. Used only with width and right justification.
	if( flags & FLAGS_ZERO ){
		pad = '0';
	}else{
		pad = ' ';
	}
	
	// Justify-left flag overrides width.
	if( flags & FLAGS_MINUS ){
		width = 0;
	}
	
	// If width is greater than precision then pad with selected character.
	while( (width > prec) && (width > num_digits) ){
		putc(pad);
		(*count)++;
		width--;
	}
	
	// Always pad up to specified precision.
	while( prec > num_digits ){
		putc('0');
		(*count)++;
		prec--;
	}
	
	// Divider is uinteger rounded up to the next order in the selected base.
	while( uinteger / divider ){
		divider *= base;
	}
	
	// Print uinteger.
	while( --num_digits ){
		divider /= base;
		putc("0123456789abcdef"[uinteger / divider]);
		uinteger %= divider;
		(*count)++;
	}
	putc("0123456789abcdef"[uinteger]);
}

static void
printstring(void (*putc)(uint16_t c), char *string,
			uint32_t prec, uint32_t *count)
{
	if( !prec || prec > MAX_STRING ){
		prec = MAX_STRING;
	}
	
	while( (*string != '\0') && (prec != 0) ){
		putc(*string);
		string++;
		(*count)++;
		prec--;
	}
}


